package bma.common.netty.webserver;

import java.util.List;

import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
import org.jboss.netty.handler.codec.frame.TooLongFrameException;
import org.jboss.netty.handler.codec.http.HttpChunkAggregator;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpRequestDecoder;
import org.jboss.netty.handler.codec.http.HttpResponseEncoder;
import org.jboss.netty.handler.codec.http.HttpResponseStatus;
import org.jboss.netty.handler.codec.http.QueryStringDecoder;
import org.jboss.netty.handler.stream.ChunkedWriteHandler;

import bma.common.langutil.ai.stack.AIStackROOT;
import bma.common.langutil.core.ExceptionUtil;
import bma.common.langutil.core.SizeUtil;
import bma.common.langutil.core.SizeUtil.Unit;
import bma.common.langutil.log.acclog.AccessLog;
import bma.common.langutil.log.acclog.AccessLogFactory;
import bma.common.langutil.log.acclog.AccessLogFactorySimple;
import bma.common.netty.NettyServer;
import bma.common.netty.webserver.common.RequestContextCommon;
import bma.common.netty.webserver.common.RequestDispatcherRoot;

public class NettyWebServer extends NettyServer {

	final org.slf4j.Logger log = org.slf4j.LoggerFactory
			.getLogger(NettyWebServer.class);

	protected int contentMaxLength = 10 * 1024 * 1024;
	protected AccessLogFactory accessLogFactory;
	protected RequestDispatcherRoot rootDispatcher = new RequestDispatcherRoot();

	public NettyWebServer() {
		super();
	}

	public RequestDispatcherRoot getRootDispatcher() {
		return rootDispatcher;
	}

	public int getContentMaxLength() {
		return contentMaxLength;
	}

	public void setContentMaxLength(int maxLength) {
		this.contentMaxLength = maxLength;
	}

	public void setContentSize(String sz) {
		try {
			this.contentMaxLength = (int) SizeUtil.convert(sz, 1024, Unit.B);
		} catch (Exception e) {
			throw ExceptionUtil.throwRuntime(e);
		}
	}

	public void addDispatcherList(List<RequestDispatcher> list) {
		rootDispatcher.addDispatcherList(list);
	}
	
	public void setDispatcherList(List<RequestDispatcher> list) {
		rootDispatcher.addDispatcherList(list);
	}

	public void addProcessorList(List<RequestProcessor> list) {
		rootDispatcher.addProcessorList(list);
	}
	
	public void setProcessorList(List<RequestProcessor> list) {
		rootDispatcher.addProcessorList(list);
	}

	public void setDispatcher(RequestDispatcher dis) {
		rootDispatcher.setDispatcher(dis);
	}

	public void setProcessor(RequestProcessor p) {
		rootDispatcher.setProcessor(p);
	}

	public void addDispatcher(RequestDispatcher dis) {
		rootDispatcher.addDispatcher(dis);
	}

	public void addProcessor(RequestProcessor p) {
		rootDispatcher.addProcessor(p);
	}

	public AccessLogFactory getAccessLogFactory() {
		return accessLogFactory;
	}

	public void setAccessLogFactory(AccessLogFactory accessLogFactory) {
		this.accessLogFactory = accessLogFactory;
	}

	@Override
	protected void beforeBuildPipeline(ChannelPipeline pipeline) {
		super.beforeBuildPipeline(pipeline);
		pipeline.addLast("decoder", new HttpRequestDecoder());
		pipeline.addLast("aggregator",
				new HttpChunkAggregator(contentMaxLength));
		pipeline.addLast("encoder", new HttpResponseEncoder());
		pipeline.addLast("chunkedWriter", new ChunkedWriteHandler());
		pipeline.addLast("main", new Handler());
	}

	private static final String LTAG = "Server";

	public class Handler extends SimpleChannelUpstreamHandler {

		@Override
		public void messageReceived(final ChannelHandlerContext nctx,
				MessageEvent e) throws Exception {
			HttpRequest request = (HttpRequest) e.getMessage();
			if (accessLogFactory == null) {
				accessLogFactory = new AccessLogFactorySimple(log);
			}

			AccessLog alog = accessLogFactory.create();
			QueryStringDecoder dec = new QueryStringDecoder(request.getUri());
			final String path = dec.getPath();

			final RequestContext ctx = new RequestContextCommon(rootDispatcher,
					nctx.getChannel(), request, path, alog);
			nctx.setAttachment(ctx);

			if (alog.isInfoEnabled()) {
				alog.info(LTAG, "start", request.getUri());
			}

			if (path == null) {
				WebServerUtil.endWithStatus(LTAG, ctx,
						HttpResponseStatus.FORBIDDEN);
				return;
			}

			AIStackROOT<Boolean> stack = new AIStackROOT<Boolean>() {

				@Override
				public boolean end(Boolean result, Throwable t) {
					if (t != null) {
						if (log.isWarnEnabled()) {
							log.warn("process fail - " + path, t);
						}
						WebServerUtil.endWithError(LTAG, ctx, t);
						return true;
					}
					if (!result) {
						if (log.isDebugEnabled()) {
							log.debug("not found - " + path);
						}
						WebServerUtil.endWithNotFound(LTAG, ctx);
					}
					return true;
				}
			};
			try {
				if (log.isDebugEnabled()) {
					log.debug("dispatch - {}", path);
				}
				if (!rootDispatcher.dispatch(stack, ctx)) {
					if (log.isDebugEnabled()) {
						log.debug("not found -- " + path);
					}
					WebServerUtil.endWithNotFound(LTAG, ctx);
				}
			} catch (Exception err) {
				if (log.isWarnEnabled()) {
					log.warn("dispatch fail - " + path, err);
				}
				WebServerUtil.endWithError(LTAG, ctx, err);
			}

		}

		@Override
		public void exceptionCaught(ChannelHandlerContext nctx, ExceptionEvent e)
				throws Exception {
			Channel ch = e.getChannel();
			Throwable cause = e.getCause();

			RequestContext ctx = (RequestContext) nctx.getAttachment();
			if (ctx == null) {
				ctx = new RequestContextCommon(null, nctx.getChannel(), null,
						null, null);
			}

			if (cause instanceof TooLongFrameException) {
				if (log.isWarnEnabled()) {
					log.warn(cause.getMessage());
				}
				WebServerUtil.endWithStatus(LTAG, ctx,
						HttpResponseStatus.BAD_REQUEST);
				return;
			}

			if (ch.isConnected()) {
				if (log.isWarnEnabled()) {
					log.warn("process request fail", cause);
				}
				WebServerUtil.endWithError(LTAG, ctx, cause);
			}
		}

	}
}
